package com.lemon.web.advice;

import com.lemon.exception.base.BussinessException;
import com.lemon.web.dto.BaseResponse;
import com.lemon.web.util.ResponseUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.lang.Nullable;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author lemon
 * @version 1.0
 * @description: ResponseEntityExceptionHandler 全局异常处理
 * RequestBodyAdvice  @RequestBody之前进行的操作
 * ResponseBodyAdvice @ResponseBody之后进行的操作，修饰返回结果
 * @date Create by lemon on 2020-07-02 11:00
 */
@Slf4j
@Order(1)
@RestControllerAdvice
@ConditionalOnProperty(name="unified.exception.enabled", havingValue = "true", matchIfMissing = true)
public class DefaultResponseEntityExceptionControllerAdvice extends ResponseEntityExceptionHandler {
    /**
     * @param request
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:50
     */
    @ExceptionHandler(value = BussinessException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public BaseResponse bussinessExceptionHandler(HttpServletRequest request, BussinessException e) {
        log.error(e.getMessage(), e);
        return new BaseResponse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage(), null);
    }

    /**
     * @param req
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:50
     */
    @ExceptionHandler(value = ResourceNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @ResponseBody
    public BaseResponse resourceNotFoundExceptionHandler(HttpServletRequest req, ResourceNotFoundException e) {
        String err = String.format("%s not found", req.getRequestURI());
        log.debug(err, e);
        return new BaseResponse(String.valueOf(HttpStatus.NOT_FOUND.value()), err, null);
    }

    /**
     * @param request
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:50
     */
    @ExceptionHandler(value = IllegalArgumentException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public BaseResponse illegalArugmentExceptionHandler(HttpServletRequest request, IllegalArgumentException e) {
        log.warn(e.getMessage(), e);
        return new BaseResponse(String.valueOf(HttpStatus.BAD_REQUEST.value()), e.getMessage(), null);
    }

    /**
     * @param request
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:50
     */
    @ExceptionHandler(value = IllegalStateException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ResponseBody
    public BaseResponse illegalStateExceptionHandler(HttpServletRequest request, IllegalStateException e) {
        log.warn(e.getMessage(), e);
        return new BaseResponse(String.valueOf(HttpStatus.FORBIDDEN.value()), e.getMessage(), null);
    }

    /**
     * @param req
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:49
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    @ResponseBody
    public BaseResponse constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        String errorMsg = violations.stream()
                .map((violation) -> String.format("%s: %s", violation.getPropertyPath(), violation.getMessage()))
                .collect(Collectors.joining("\n"));
        log.debug(errorMsg, e);
        return new BaseResponse(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMsg, null);
    }

    /**
     * @param request
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description
     * @author lemon
     * @date 2020-07-06 08:49
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ResponseBody
    public BaseResponse exceptionHandler(HttpServletRequest request, Exception e) {
        log.error(e.getMessage(), e);
        return new BaseResponse(String.valueOf(HttpStatus.INTERNAL_SERVER_ERROR.value()), e.getMessage(), null);
    }

    /**
     * @param request
     * @param e
     * @return com.lemon.web.dto.BaseResponse
     * @description 参数格式和 @RequestParam 不符
     * @author lemon
     * @date 2020-07-06 08:45
     */
    @ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public BaseResponse methodArgumentTypeMismatchExceptionHandler(HttpServletRequest request, MethodArgumentTypeMismatchException e) {
        String err = String.format("field: %s. %s", e.getName(), e.getMessage());
        log.warn(err, e);
        return new BaseResponse(String.valueOf(HttpStatus.BAD_REQUEST.value()), err, null);
    }

    /**
     * Customize the response for HttpMessageNotReadableException.
     * <p>This method delegates to {@link #handleExceptionInternal}.
     *
     * @param ex      the exception
     * @param headers the headers to be written to the response
     * @param status  the selected response status
     * @param request the current request
     * @return a {@code ResponseEntity} instance
     */
    @Override
    protected ResponseEntity<Object> handleHttpMessageNotReadable(
            HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        String message = "Required request body is missing";
        log.debug(message, ex);
        return handleExceptionInternal(ex, message, headers, status, request);
    }

    /**
     * Customize the response for MethodArgumentNotValidException.
     * <p>This method delegates to {@link #handleExceptionInternal}.
     * <p>@RequestBody 校验违反约束
     *
     * @param ex      the exception
     * @param headers the headers to be written to the response
     * @param status  the selected response status
     * @param request the current request
     * @return a {@code ResponseEntity} instance
     */
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        String errorFieldMessage = ResponseUtil.getErrorFieldMessage(ex, ex.getBindingResult().getAllErrors());
        log.debug(errorFieldMessage, ex);
        return handleExceptionInternal(ex, errorFieldMessage, headers, status, request);
    }

    /**
     * Customize the response for BindException.
     * <p>This method delegates to {@link #handleExceptionInternal}.
     * <p>表单对象校验
     *
     * @param ex      the exception
     * @param headers the headers to be written to the response
     * @param status  the selected response status
     * @param request the current request
     * @return a {@code ResponseEntity} instance
     */
    @Override
    protected ResponseEntity<Object> handleBindException(
            BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        String errorFieldMessage = ResponseUtil.getErrorFieldMessage(ex, ex.getBindingResult().getAllErrors());
        log.debug(errorFieldMessage, ex);
        return handleExceptionInternal(ex, errorFieldMessage, headers, status, request);
    }

    /**
     * A single place to customize the response body of all exception types.
     * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE}
     * request attribute and creates a {@link ResponseEntity} from the given
     * body, headers, and status.
     *
     * @param ex      the exception
     * @param body    the body for the response
     * @param headers the headers for the response
     * @param status  the response status
     * @param request the current request
     */
    @Override
    protected ResponseEntity<Object> handleExceptionInternal(
            Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }

        String message = ex.getMessage();

        if (body != null) {
            message = body.toString();
        }

        log.error(String.format("exception:%s, message:%s, class:%s", ex.getMessage(), message, ex.getClass()), ex);

        BaseResponse responseBody = new BaseResponse(String.valueOf(status.value()), message, null);
        return new ResponseEntity<>(responseBody, headers, status);
    }
}
